package com.flow.framework.persistence.helper;

import com.flow.framework.common.error.SystemErrorCode;
import com.flow.framework.common.exception.CheckedException;
import com.flow.framework.common.util.verify.VerifyUtil;
import com.flow.framework.persistence.system.listener.lifecycle.DataSourceTransactionMgrHolder;
import io.seata.core.exception.TransactionException;
import io.seata.tm.api.GlobalTransaction;
import io.seata.tm.api.GlobalTransactionContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

/**
 * 事务工具类
 * 本地事务：不管当前是否存在事务，都会重新开启新的事务
 * 分布式事务：如果外层事务已经开启，则继续使用已经开启的分布式事务，不会重新开启新的分布式事务
 *
 * @author luoguopiao
 * @version 0.0.1
 * @date 2022/4/5
 */
@Slf4j
public class TransactionHelper {

    /**
     * 开启本地新事务并提交，注意：如果外层事务已经开启并产生快照数据，则新开启的事务提交的数据在外层事务中可能是无法查询到最新变化的
     *
     * @param callback callback
     */
    public static void localNewTransactionVoidExec(IVoidTransactionCallback callback) {
        if (VerifyUtil.isEmpty(callback)) {
            log.error("transaction callback is null.");
            throw new CheckedException(SystemErrorCode.PARAMS_ERROR);
        }
        DataSourceTransactionManager transactionManager = DataSourceTransactionMgrHolder.getLocalTransactionManager();
        TransactionStatus transactionStatus = getTransactionStatus(transactionManager);
        try {
            callback.callback();
            transactionManager.commit(transactionStatus);
        } catch (Exception e) {
            log.error("new transaction execute error", e);
            transactionManager.rollback(transactionStatus);
            if (e instanceof CheckedException) {
                throw (CheckedException) e;
            }
            throw new CheckedException(SystemErrorCode.UNEXPECTED_ERROR, "transaction execute error.", e);
        } finally {
            // 如果不flush，则该事务可能会一直占用该数据库连接，导致数据库连接耗尽
            transactionStatus.flush();
        }
    }

    /**
     * 开启分布式事务并提交，注意：如果外层事务已经开启，则继续使用已经开启的分布式事务
     *
     * @param timeout  事务超时时间（单位为秒）
     * @param callback callback
     */
    public static void distributedTransactionVoidExec(int timeout, IVoidTransactionCallback callback) {
        GlobalTransaction globalTransaction = GlobalTransactionContext.getCurrentOrCreate();
        try {
            globalTransaction.begin(timeout);
            callback.callback();
            globalTransaction.commit();
        } catch (Exception e) {
            throwDistributedTransactionException(globalTransaction, e);
        }
    }

    /**
     * 开启分布式事务并提交，默认超时时间为60秒，注意：如果外层事务已经开启，则继续使用已经开启的分布式事务
     *
     * @param callback callback
     */
    public static void distributedTransactionVoidExec(IVoidTransactionCallback callback) {
        GlobalTransaction globalTransaction = GlobalTransactionContext.getCurrentOrCreate();
        try {
            globalTransaction.begin();
            callback.callback();
            globalTransaction.commit();
        } catch (Exception e) {
            throwDistributedTransactionException(globalTransaction, e);
        }
    }


    /**
     * 开启本地新事务并提交，注意：如果外层事务已经开启并产生快照数据，则新开启的事务提交的数据在外层事务中可能是无法查询到最新变化的
     *
     * @param callback callback
     * @param <V>      返回对象
     * @return 返回执行结果
     */
    public static <V> V localNewTransactionResultExec(IResultTransactionCallback<V> callback) {
        if (VerifyUtil.isEmpty(callback)) {
            log.error("transaction callback is null.");
            throw new CheckedException(SystemErrorCode.PARAMS_ERROR);
        }
        DataSourceTransactionManager transactionManager = DataSourceTransactionMgrHolder.getLocalTransactionManager();
        TransactionStatus transactionStatus = getTransactionStatus(transactionManager);
        try {
            V result = callback.callback();
            transactionManager.commit(transactionStatus);
            return result;
        } catch (Exception e) {
            log.error("new transaction execute error", e);
            transactionManager.rollback(transactionStatus);
            if (e instanceof CheckedException) {
                throw (CheckedException) e;
            }
            throw new CheckedException(SystemErrorCode.UNEXPECTED_ERROR, "transaction execute error.", e);
        } finally {
            // 如果不flush，则该事务可能会一直占用该数据库连接，导致数据库连接耗尽
            transactionStatus.flush();
        }
    }

    /**
     * 开启分布式事务并提交，默认超时时间为60秒，注意：如果外层事务已经开启，则继续使用已经开启的分布式事务
     *
     * @param callback callback
     * @param <V>      V
     * @return
     */
    public static <V> V distributedTransactionResultExec(IResultTransactionCallback<V> callback) {
        GlobalTransaction globalTransaction = GlobalTransactionContext.getCurrentOrCreate();
        try {
            globalTransaction.begin();
            V result = callback.callback();
            globalTransaction.commit();
            return result;
        } catch (Exception e) {
            throwDistributedTransactionException(globalTransaction, e);
        }
        throw new CheckedException(SystemErrorCode.UNEXPECTED_ERROR, "distributed transaction execute error.");
    }

    /**
     * 开启分布式事务并提交，注意：如果外层事务已经开启，则继续使用已经开启的分布式事务
     *
     * @param timeout  事务超时时间（单位为秒）
     * @param callback callback
     * @param <V>      V
     * @return
     */
    public static <V> V distributedTransactionResultExec(int timeout, IResultTransactionCallback<V> callback) {
        GlobalTransaction globalTransaction = GlobalTransactionContext.getCurrentOrCreate();
        try {
            globalTransaction.begin(timeout);
            V result = callback.callback();
            globalTransaction.commit();
            return result;
        } catch (Exception e) {
            throwDistributedTransactionException(globalTransaction, e);
        }
        throw new CheckedException(SystemErrorCode.UNEXPECTED_ERROR, "distributed transaction execute error.");
    }

    private static void throwDistributedTransactionException(GlobalTransaction globalTransaction, Exception e) {
        log.error("distributed transaction execute error", e);
        try {
            globalTransaction.rollback();
        } catch (TransactionException te) {
            log.error("rollback distributed transaction execute error", te);
            throw new CheckedException(SystemErrorCode.UNEXPECTED_ERROR, "rollback distributed transaction execute error.", te);
        }
        if (e instanceof CheckedException) {
            throw (CheckedException) e;
        }
        throw new CheckedException(SystemErrorCode.UNEXPECTED_ERROR, "distributed transaction execute error.", e);
    }

    private static TransactionStatus getTransactionStatus(DataSourceTransactionManager transactionManager) {
        if (VerifyUtil.isEmpty(transactionManager)) {
            log.error("transaction manager is null.");
            throw new CheckedException(SystemErrorCode.PARAMS_ERROR);
        }
        //1.获取事务定义
        DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
        //2.设置事务隔离级别，开启新事务
        definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        //3.获得事务状态，相当于开启事物
        return transactionManager.getTransaction(definition);
    }

    public interface IVoidTransactionCallback {
        /**
         * 回调方法
         */
        void callback();
    }

    public interface IResultTransactionCallback<V> {
        /**
         * 回调方法
         *
         * @return 返回执行结果
         */
        V callback();
    }
}
